/*
    BibleNotebook - A bible reader software
    Copyright (C) 2014  Williams Fernando Lopez

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
 */

package com.simplebible.support.sword;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import org.crosswire.common.util.Language;
import org.crosswire.common.xml.SAXEventProvider;
import org.crosswire.common.xml.XMLUtil;
import org.crosswire.jsword.book.BookData;
import org.crosswire.jsword.book.OSISUtil;
import org.crosswire.jsword.book.sword.BookType;
import org.crosswire.jsword.book.sword.ConfigEntryType;
import org.crosswire.jsword.book.sword.SwordBookMetaData;
import org.crosswire.jsword.passage.Key;
import org.jdom.Content;
import org.jdom.Element;
import org.jdom.Text;
import org.xml.sax.SAXException;
import com.simplebible.bible.BibleReader;
import wl.util.Utils;
import com.simplebible.util.VirtualFolder;
import com.simplebible.dictionary.objects.DictDefinition;
import com.simplebible.dictionary.DictionaryFile;
import com.simplebible.bible.objects.Abreviation;
import com.simplebible.bible.objects.Bible;
import com.simplebible.bible.objects.Book;
import com.simplebible.bible.objects.Chapter;
import com.simplebible.bible.objects.Note;
import com.simplebible.objects.Preferences;
import com.simplebible.bible.objects.Referencia;
import com.simplebible.bible.objects.Title;
import com.simplebible.bible.objects.Verse;
/**
 *
 * @author Williams Lopez
 */
public class FromSwordConverter {


    private static HashMap<String,String> bookKeyMap;
    private static String[] noBiblicElements = {"note","reference","title"};


    public static HashMap<String,String> getBookKeyMap(){
        if(bookKeyMap==null){
            bookKeyMap = new HashMap<String, String>();
            bookKeyMap.put("Gen".toLowerCase(),"gen");
            bookKeyMap.put("Exod".toLowerCase(),"exo");
            bookKeyMap.put("Lev".toLowerCase(),"lev");
            bookKeyMap.put("Num".toLowerCase(),"num");
            bookKeyMap.put("Deut".toLowerCase(),"deu");
            bookKeyMap.put("Josh".toLowerCase(),"jos");
            bookKeyMap.put("Judg".toLowerCase(),"jdg");
            bookKeyMap.put("Ruth".toLowerCase(),"rut");
            bookKeyMap.put("1Sam".toLowerCase(),"1sa");
            bookKeyMap.put("2Sam".toLowerCase(),"2sa");
            bookKeyMap.put("1Kgs".toLowerCase(),"1ki");
            bookKeyMap.put("2Kgs".toLowerCase(),"2ki");
            bookKeyMap.put("1Chr".toLowerCase(),"1ch");
            bookKeyMap.put("2Chr".toLowerCase(),"2ch");
            bookKeyMap.put("Ezra".toLowerCase(),"ezr");
            bookKeyMap.put("Neh".toLowerCase(),"neh");
            bookKeyMap.put("Esth".toLowerCase(),"est");
            bookKeyMap.put("Job".toLowerCase(),"job");
            bookKeyMap.put("Ps".toLowerCase(),"psa");
            bookKeyMap.put("Prov".toLowerCase(),"pro");
            bookKeyMap.put("Eccl".toLowerCase(),"ecc");
            bookKeyMap.put("Song".toLowerCase(),"sng");
            bookKeyMap.put("Isa".toLowerCase(),"isa");
            bookKeyMap.put("Jer".toLowerCase(),"jer");
            bookKeyMap.put("Lam".toLowerCase(),"lam");
            bookKeyMap.put("Ezek".toLowerCase(),"ezk");
            bookKeyMap.put("Dan".toLowerCase(),"dan");
            bookKeyMap.put("Hos".toLowerCase(),"hos");
            bookKeyMap.put("Joel".toLowerCase(),"jol");
            bookKeyMap.put("Amos".toLowerCase(),"amo");
            bookKeyMap.put("Obad".toLowerCase(),"oba");
            bookKeyMap.put("Jonah".toLowerCase(),"jon");
            bookKeyMap.put("Mic".toLowerCase(),"mic");
            bookKeyMap.put("Nah".toLowerCase(),"nam");
            bookKeyMap.put("Hab".toLowerCase(),"hab");
            bookKeyMap.put("Zeph".toLowerCase(),"zep");
            bookKeyMap.put("Hag".toLowerCase(),"hag");
            bookKeyMap.put("Zech".toLowerCase(),"zec");
            bookKeyMap.put("Mal".toLowerCase(),"mal");
            bookKeyMap.put("Matt".toLowerCase(),"mat");
            bookKeyMap.put("Mark".toLowerCase(),"mrk");
            bookKeyMap.put("Luke".toLowerCase(),"luk");
            bookKeyMap.put("John".toLowerCase(),"jhn");
            bookKeyMap.put("Acts".toLowerCase(),"act");
            bookKeyMap.put("Rom".toLowerCase(),"rom");
            bookKeyMap.put("1Cor".toLowerCase(),"1co");
            bookKeyMap.put("2Cor".toLowerCase(),"2co");
            bookKeyMap.put("Gal".toLowerCase(),"gal");
            bookKeyMap.put("Eph".toLowerCase(),"eph");
            bookKeyMap.put("Phil".toLowerCase(),"php");
            bookKeyMap.put("Col".toLowerCase(),"col");
            bookKeyMap.put("1Thess".toLowerCase(),"1th");
            bookKeyMap.put("2Thess".toLowerCase(),"2th");
            bookKeyMap.put("1Tim".toLowerCase(),"1ti");
            bookKeyMap.put("2Tim".toLowerCase(),"2ti");
            bookKeyMap.put("Titus".toLowerCase(),"tit");
            bookKeyMap.put("Phlm".toLowerCase(),"phm");
            bookKeyMap.put("Heb".toLowerCase(),"heb");
            bookKeyMap.put("Jas".toLowerCase(),"jas");
            bookKeyMap.put("1Pet".toLowerCase(),"1pe");
            bookKeyMap.put("2Pet".toLowerCase(),"2pe");
            bookKeyMap.put("1John".toLowerCase(),"1jn");
            bookKeyMap.put("2John".toLowerCase(),"2jn");
            bookKeyMap.put("3John".toLowerCase(),"3jn");
            bookKeyMap.put("Jude".toLowerCase(),"jud");
            bookKeyMap.put("Rev".toLowerCase(),"rev");
        }
        return bookKeyMap;
    }

    public static void swordToSimpleBible(File orig, File dest, ActionListener notif) throws Exception{

        int completado = 0;

        SwordBookMetaData md = new SwordBookMetaData(orig, "booksword", orig.getParentFile().getParentFile().toURI());
        
        BookType modtype = md.getBookType();
        if (modtype == null || modtype.getBookCategory() == null){
            throw new Exception("Error");
        }
        org.crosswire.jsword.book.Book swb= modtype.createBook(md);

        Bible bible = readSwordBibleInfo(md);

        completado++;

        notif.actionPerformed(new ActionEvent("", completado, "Bible information readed ok."));
        notif.actionPerformed(new ActionEvent("", completado, "Transforming bible format Sword to BibleNotebook..."));

        for(int i=0;i<bible.getBooks().size();i++){
            Book book = bible.getBooks().get(i);

            for(int j=0;j<book.getChapters().size();j++){
                Chapter chapter = book.getChapters().get(j);
                List<Title> titles = new ArrayList<Title>();
                List<Verse> listVerses = new ArrayList<Verse>();
                try {
                    int verseIndex = 1;
                    for (int k = 0; k < 200; k++) {
                        org.crosswire.jsword.passage.Verse v = new org.crosswire.jsword.passage.Verse(i+1, j+1, k+1);
                        BookData bd = new BookData(swb, v);
                        Element osisFrag = bd.getOsisFragment();
                        String textVerse = "";
                        Verse verso = new Verse();


                        verso.setHumanName(verseIndex+"");
                        verso.setId(verseIndex);
                        verso.setOrden(verseIndex);
                        verso.setAbreviation(chapter.getAbreviation().cloneChanging(Abreviation.FIELD_VERSE, Integer.toString(k+1)));

                        try {
                            getVerseDataAndNotes(osisFrag, verso);
                        } catch (Exception e) {
                            Utils.printToLog("No se pudo obtener el verso y las notas con el procedimiento normal:"+e.getMessage(), e, Preferences.LOG_LEVEL_ERROR);
                            textVerse = OSISUtil.getCanonicalText(osisFrag);
                            verso.setText(textVerse);
                            try {
                                List<Referencia> ref = new ArrayList<Referencia>();
                                obtenerReferencias(verso, ref, osisFrag);
                                if (!ref.isEmpty()) {
                                    verso.setRefCruzadas(ref);
                                }
                            } catch (Exception ex) {
                                Utils.printToLog("No se pudo obtener las referencias con el procedimiento de contingencia:"+e.getMessage(), e, Preferences.LOG_LEVEL_ERROR);
                            }
                        }                        
                        listVerses.add(verso);
                        verseIndex++;

                        //Tratando de obtener titulos
                        try {
                            obtenerTitulos(verso,titles,osisFrag);
                        } catch (Exception e) {
                            Utils.printToLog("Error al obtener los titulos:"+e.getMessage(), e, Preferences.LOG_LEVEL_ERROR);
                        }

                    }
                } catch (org.crosswire.jsword.passage.NoSuchVerseException e) {
                    //e.printStackTrace();
                }
                chapter.setVerses(listVerses);
                chapter.setTitles(titles);
            }

            notif.actionPerformed(new ActionEvent("", ++completado, "Transforming bible format Sword to BibleNotebook..."));
        }

        notif.actionPerformed(new ActionEvent("", completado, "Bible format transformed ok."));
        notif.actionPerformed(new ActionEvent("", completado, "Creating bible file..."));



        //Printing output file
        VirtualFolder vf = new VirtualFolder(BibleReader.root +File.separator+ bible.getAbreviation().getBible(), false, 512, 300, 50000, 5000);

        for(Book book: bible.getBooks()){

            if(book.getId()%3==0){
                notif.actionPerformed(new ActionEvent("", ++completado, "Creating bible file..."));
            }

            for(Chapter ch : book.getChapters()){
                String str = BibleReader.serializeChapter(ch);
                vf.write(ch.getAbreviation().getBook()+BibleReader.separatorName+ch.getAbreviation().getChapter()+".sb", str.getBytes());
            }
        }

        notif.actionPerformed(new ActionEvent("", ++completado, "Creating bible file..."));
        //Building File Info 1
        StringBuilder sb = new StringBuilder();
        sb.append(bible.getHumanName());
        sb.append("\n");
        sb.append(bible.getHumanName());
        sb.append("\n");
        for(int i=0;i<bible.getBooks().size();i++){
            Book book = bible.getBooks().get(i);
            sb.append(book.getAbreviation().getBook());
            sb.append(",");
            sb.append(book.getHumanName());
            sb.append(",");
            sb.append(book.getChapters().size());
            sb.append("\n");
        }
        vf.write(bible.getAbreviation().getBible(), sb.toString().getBytes());


        notif.actionPerformed(new ActionEvent("", ++completado, "Creating bible file..."));
        //Building File Info 2 (NEW)
        vf.write(bible.getAbreviation().getBible()+".info",BibleReader.serializeBibleInfo(bible).getBytes());
        vf.close();

        swb.deactivate(null);
        notif.actionPerformed(new ActionEvent("", 100, "Bible file created ok"));
    }

    public static Bible readSwordBibleInfo(SwordBookMetaData md) throws Exception{
        String encoding = (String)md.getProperty(ConfigEntryType.ENCODING);
        String compress = (String)md.getProperty(ConfigEntryType.COMPRESS_TYPE);
        String minversion  = (String)md.getProperty(ConfigEntryType.MINIMUM_VERSION);

        String language = ((Language)md.getProperty(ConfigEntryType.LANG)).getCode();
        String initials    = md.getInitials();
        String biblename   = md.getName();
        String copyRight   = (String)md.getProperty(ConfigEntryType.COPYRIGHT);
        String copyRightContactName = (String)md.getProperty(ConfigEntryType.COPYRIGHT_CONTACT_NAME);
        String copyRightContactNote = (String)md.getProperty(ConfigEntryType.COPYRIGHT_CONTACT_NOTES);
        String copyRightContactAdrs = (String)md.getProperty(ConfigEntryType.COPYRIGHT_CONTACT_ADDRESS);
        String about = (String)md.getProperty(ConfigEntryType.ABOUT);


        //Leyendo propiedades
        Properties props = new Properties();
        props.load(FromSwordConverter.class.getResourceAsStream("/simplebible/support/sword/sword.properties"));
        //Installed version
        String installedVersion = props.getProperty("installedVersion");


        if(!compress.equalsIgnoreCase("ZIP")){
            throw new Exception("Not supported sword-bible compress ["+compress+"]");
        }
        if(!isValidVersion(installedVersion, minversion)){
            throw new Exception("Not supported sword-bible version [Minimum Version: "+minversion+", Installed: "+installedVersion+"]");
        }

        if(initials == null || initials.trim().equals("")){
            throw new Exception("Cant find bible initials version");
        }

        Abreviation abrv = new Abreviation();
        abrv.setBible(initials.toLowerCase());

        Bible bible = new Bible();
        bible.setAbreviation(abrv);
        bible.setLanguage(language);
        bible.setAbout(about);
        bible.setCopyRight(copyRight);
        bible.setCopyRightContactAdrs(copyRightContactAdrs);
        bible.setCopyRightContactName(copyRightContactName);
        bible.setCopyRightContactNote(copyRightContactNote);
        bible.setEncoding(compress);

        byte[] bibleFileIfoContent = getBibleFileInfo(language);
        BibleReader.setBooksFromFileInfo(bible, initials, bibleFileIfoContent);

        bible.setHumanName(biblename);


        return bible;

    }

    private static boolean isValidVersion(String installedVersion,String minVersion){
        try{
            installedVersion = installedVersion.trim().replaceAll("[.]", "");
            installedVersion = installedVersion.trim().replaceAll("[ \t\n\r]", "");
            minVersion = minVersion.trim().replaceAll("[.]", "");
            minVersion = minVersion.trim().replaceAll("[ \t\n\r]", "");
            int iv1  = Integer.parseInt(installedVersion);
            int iv2 = Integer.parseInt(minVersion);
            return iv1 >= iv2;
        }catch(Exception e){
            return true;
        }
    }

    private static byte[] getBibleFileInfo(String lang) throws IOException{

        if((!lang.equals("es")) && (!lang.equals("en"))){
            lang = "en";
        }

        String name = "/simplebible/support/sword/"+lang+".bibleinfo";

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        InputStream  is = FromSwordConverter.class.getResourceAsStream(name);

        int read = -1;
        while((read=is.read())!=-1){
            os.write(read);
        }

        return os.toByteArray();
    }

    private static void obtenerTitulos(Verse verse,List<Title> titles, Object oFrag) {

        if(!(oFrag instanceof Element)) return;

        Element osisFrag = (Element)oFrag;

        if (osisFrag.getName().equalsIgnoreCase("title")) {
            String text = OSISUtil.getPlainText(osisFrag);
            if (!text.endsWith(verse.getAbreviation().getChapter() + ":" + verse.getAbreviation().getVerse())) {
                Title t = new Title();
                t.setPosition(Integer.toString(verse.getOrden() - 1));
                t.setHumanName(text);
                titles.add(t);
            }
        }

        List children = osisFrag.getChildren();
        for(Object obj : children){
            Element e = (Element)obj;
            obtenerTitulos(verse, titles, e);
        }
    }


    private static void obtenerReferencias(Verse verse, List<Referencia> refs, Object oFrag) throws Exception{
        if(!(oFrag instanceof Element)) return;
        Element osisFrag = (Element)oFrag;
        if (osisFrag.getName().equalsIgnoreCase("reference")) {
            //if (osisFrag.getAttribute("type")!=null && osisFrag.getAttribute("type").getValue().equalsIgnoreCase("crossReference")) {
                //try{
                    refs.add(parseSwordReference(osisFrag.getAttributeValue("osisRef")));
                //}catch(Exception e){}
            //}
        }
        List children = osisFrag.getChildren();
        for(Object obj : children){
            Element e = (Element)obj;
            obtenerReferencias(verse, refs, e);
        }
    }


    private static Referencia parseSwordReference(String str) throws Exception{
        Referencia r = new Referencia();
        r.setReferencias(new ArrayList<Abreviation>());
        String[] refs = str.split("[-,]");

        for(String sr : refs){
            sr = sr.trim();
            String[] data = sr.split("[.]");
            data[0] = data[0].trim();
            String mapped = getBookKeyMap().get(data[0].toLowerCase());
            if(mapped==null){
                Utils.printToLog(data[0]+"is not mapped",null,Preferences.LOG_LEVEL_ERROR);
                continue;
            }
            sr = sr.replace(data[0], mapped);
            r.getReferencias().add(Abreviation.parse(sr));
        }

        
        return r;
    }


    /**
     *
     * @param orig
     * @param dest
     * @param notif
     * @throws Exception
     * @deprecated No usado debido que para los diccionarios no se cambia el formato
     */
    public static void swordToSimpleDict(File orig, File dest, ActionListener notif) throws Exception{
        
        SwordBookMetaData md = new SwordBookMetaData(orig, "booksword", orig.getParentFile().getParentFile().toURI());

        DictionaryFile dict = new DictionaryFile(dest.getAbsolutePath());

        String name = md.getProperty(ConfigEntryType.DESCRIPTION)!=null?md.getProperty(ConfigEntryType.DESCRIPTION).toString():null;
        String url  = md.getProperty(ConfigEntryType.COPYRIGHT_CONTACT_ADDRESS)!=null? md.getProperty(ConfigEntryType.COPYRIGHT_CONTACT_ADDRESS).toString():null;
        String copyr= md.getProperty(ConfigEntryType.COPYRIGHT)!=null?md.getProperty(ConfigEntryType.COPYRIGHT).toString():null;
        String desc = md.getProperty(ConfigEntryType.ABOUT)!=null ? md.getProperty(ConfigEntryType.ABOUT).toString():null;
        String lang = md.getProperty(ConfigEntryType.LANG)!=null ? md.getProperty(ConfigEntryType.LANG).toString():"es";

        dict.setDictionaryInfo(name,url,copyr,desc,lang);

        BookType modtype = md.getBookType();
        if (modtype == null || modtype.getBookCategory() == null){
            throw new Exception("Error");
        }
        org.crosswire.jsword.book.Book swb= modtype.createBook(md);

        Key keys = swb.getGlobalKeyList();
        String[][] buffer = new String[2][1000];
        DictDefinition def = new DictDefinition();
        int words = 0;
        int bufferCount=0;
        int size = keys.getCardinality();

        // Get a verse iterator

        Iterator iter = keys.iterator();
        while (iter.hasNext())
        {
            Key key = (Key) iter.next();
            try
            {
                BookData bdata = new BookData(swb, key);
                SAXEventProvider osissep = bdata.getSAXEventProvider();
                buffer[0][bufferCount] = key.getName();
                buffer[1][bufferCount] = XMLUtil.writeToString(osissep);
                words++;
                bufferCount++;

                if(words%buffer[0].length==0){
                    notif.actionPerformed(new ActionEvent("", (int)(((double)words/(double)size)*100),key.getName()+"...<br/>"+words+"/"+size+" words"));
                    for(int i=0;i<bufferCount;i++){
                        try{
                            def.setWordKey(null);
                            def.setWord(buffer[0][i]);
                            def.setDefinitions(new String[]{buffer[1][i]});
                            dict.put(def);
                        }catch(Exception e){
                            Utils.printToLog("Error al agregar la definicion "+buffer[0][i], e, Preferences.LOG_LEVEL_ERROR);
                        }
                    }
                    bufferCount = 0;
                }
            }
            catch (SAXException e)
            {
                Utils.printToLog("Error al convertir la definicion "+key.getName(), e, Preferences.LOG_LEVEL_ERROR);
            }
            catch(Exception e){
                Utils.printToLog("Error al convertir la definicion "+key.getName(), e, Preferences.LOG_LEVEL_ERROR);
            }
        }

        notif.actionPerformed(new ActionEvent("", (int)(((double)words/(double)size)*100),words+"/"+size+" words"));
        for (int i = 0; i < bufferCount; i++) {
            try {
                def.setWordKey(null);
                def.setWord(buffer[0][i]);
                def.setDefinitions(new String[]{buffer[1][i]});
                dict.put(def);
            } catch (Exception e) {
                Utils.printToLog("Error al agregar la definicion " + buffer[0][i], e, Preferences.LOG_LEVEL_ERROR);
            }
        }
        bufferCount = 0;



        dict.close();
        swb.deactivate(null);

        String str = "successfully completed<br/>";
        str += ""+words+" words found";
        notif.actionPerformed(new ActionEvent("", 100, str));
    }


    private static void getVerseDataAndNotes(Element elemVerse,Verse verse) throws Exception{
        List<Note> notes = new ArrayList<Note>();
        StringBuilder finalText = new StringBuilder();
        verse.setRefCruzadas(new ArrayList<Referencia>());
        verse.setNotes(new ArrayList<Note>());
        recursiveVerseAndNotesAnalisys(null,elemVerse, 1, finalText,verse);
        verse.setText(finalText.toString());
    }

    private static void recursiveVerseAndNotesAnalisys(Element parent,Object elem, int orden, StringBuilder verseTextOutput,Verse verse) throws Exception{
        if (elem instanceof Element) {
            Element element = (Element) elem;

            if (element.getName().equalsIgnoreCase("note")) {

                if (element.getAttributeValue("type").equalsIgnoreCase("crossReference")) {
                    //do nothing
                } else if (element.getAttributeValue("type").equalsIgnoreCase("explanation")) {
                    Note note = new Note();
                    note.setAbreviation(verse.getAbreviation());
                    note.setHumanName(element.getAttributeValue("n"));
                    if(note.getHumanName()==null){
                        note.setHumanName("n");
                    }
                    note.setId(verse.getNotes().size()+1);
                    note.setOrden(note.getId());
                    note.setText(OSISUtil.getPlainText(element));
                    verse.getNotes().add(note);
                    verseTextOutput.append("##");
                    verseTextOutput.append(note.getId());
                    Collection c = OSISUtil.getDeepContent(element, "catchWord");
                    if (c.size() > 0) {
                        note.setRelatedText(((Element) c.iterator().next()).getText());
                    }
                    return;
                } else {
                    Note note = new Note();
                    note.setAbreviation(verse.getAbreviation());
                    note.setHumanName(element.getAttributeValue("n"));
                    if(note.getHumanName()==null){
                        note.setHumanName("n");
                    }
                    note.setId(verse.getNotes().size()+1);
                    note.setOrden(note.getId());
                    note.setText(OSISUtil.getPlainText(element));
                    verse.getNotes().add(note);
                    verseTextOutput.append("##");
                    verseTextOutput.append(note.getId());
                    return;
                }
            } else if (element.getName().equalsIgnoreCase("title")) {
                //do nothing
            } else if (element.getName().equalsIgnoreCase("reference")) {
                Referencia ref = parseSwordReference(element.getAttributeValue("osisRef"));
                verse.getRefCruzadas().add(ref);
                ref.setPosicion("#" + verse.getRefCruzadas().size());
                verseTextOutput.append(ref.getPosicion());
                return;
            }else if (element.getName().equals("hi")){
                try{
                    Integer num = Integer.parseInt(element.getText());
                    verseTextOutput.append("*");
                    verseTextOutput.append(num);
                }catch(Exception e){
                    Utils.printToLog("Error en etiqueta hi, el texto contenido no es un numero", e, Preferences.LOG_LEVEL_ERROR);
                    
                }
                return;
            }

            List children = element.getContent();
            if (children == null || children.isEmpty()) {
                //System.out.println(indent + element.getText());
            } else {
                if (children != null) {
                    for (int i = 0; i < children.size(); i++) {
                        Object child = children.get(i);
                        recursiveVerseAndNotesAnalisys(element, child, orden + 1, verseTextOutput, verse);
                    }
                }
            }

        } else if (elem instanceof Text) {
            if (isCanonicalElement(parent)) {
                int lastIndex = verseTextOutput.length() - 1;
                String text = ((Text) elem).getText();
                if (lastIndex >= 0 && !Character.isWhitespace(verseTextOutput.charAt(lastIndex)) && !Character.isWhitespace(text.charAt(0))) {
                    verseTextOutput.append(' ');
                }
                verseTextOutput.append(text);
            }
        }
    }

    private static boolean isCanonicalElement(Content content)
    {
        boolean result = true;
        if (content instanceof Element)
        {
            Element element = (Element) content;

            // Ignore extra-biblical text
            if (containsElement(noBiblicElements, element.getName())){
                String canonical = element.getAttributeValue(OSISUtil.OSIS_ATTR_CANONICAL);
                result = Boolean.valueOf(canonical).booleanValue();
                return result;
            }
        }

        return result;
    }

    public static boolean containsElement(String[] array, String element){
        for(String s:array){
            if(s.equalsIgnoreCase(element)){
                return true;
            }
        }
        return false;
    }


}
